/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import xowl.interpreter.backends.rdf.RDFChange;
import xowl.interpreter.backends.rdf.RDFChangeset;
import xowl.interpreter.backends.rdf.RDFNode;
import xowl.interpreter.backends.rdf.RDFNodeType;
import xowl.interpreter.backends.rdf.RDFTriple;
import xowl.interpreter.backends.rdf.RDFTripleField;
import xowl.interpreter.backends.rdf.XOWLVariableNode;

/**
 *
 * @author Laurent WOUTERS
 */
public class RETENetwork {
    private static class LevelData {
        List<BetaJoinNodeTest> tests;
        List<Binder> binders;
        public LevelData() {
            this.tests = new ArrayList<BetaJoinNodeTest>();
            this.binders = new ArrayList<Binder>();
        }
    }
    
    private KnowledgeBase kb;
    private AlphaGraph entryAlpha;
    private Map<RETERule, List<LevelData>> rules;
    
    public RETENetwork() {
        this.kb = new KnowledgeBase();
        this.entryAlpha = new AlphaGraph();
        this.rules = new HashMap<RETERule, List<LevelData>>();
    }
    
    public void inject(RDFChange change) {
        TripleFact fact = kb.getFact(change.getTriple(), change.getOntology(), change.isAdd());
        if (fact == null)
            return;
        if (change.isAdd())
            entryAlpha.fire(fact);
        else
            entryAlpha.unfire(fact);
    }
    public void inject(RDFChangeset changeset) {
        Collection<TripleFact> positives = new ArrayList<TripleFact>();
        Collection<TripleFact> negatives = new ArrayList<TripleFact>();
        for (RDFChange change : changeset.getRDFChanges()) {
            TripleFact fact = kb.getFact(change.getTriple(), change.getOntology(), change.isAdd());
            if (fact == null) continue;
            if (change.isAdd())
                positives.add(fact);
            else
                negatives.add(fact);
        }
        if (!negatives.isEmpty())
            entryAlpha.unfire(negatives);
        if (!positives.isEmpty())
            entryAlpha.fire(positives);
    }
    
    public void addRule(RETERule rule) {
        BetaMemory last = addRule(rule, rule.getPositives(), rule.getNegatives());
        last.push();
    }
    public void addRules(Collection<RETERule> rules) {
        List<List<Condition>> conditions = new ArrayList<List<Condition>>();
        for (RETERule rule : rules)
            conditions.add(rule.getPositives());
        OptimizationStrategy optimizer = new NoOptimization();
        conditions = optimizer.optimize(conditions);
        Iterator<RETERule> iter = rules.iterator();
        List<BetaMemory> lasts = new ArrayList<BetaMemory>();
        for (int i=0; i!=rules.size(); i++) {
            RETERule rule = iter.next();
            lasts.add(addRule(rule, conditions.get(i), rule.getNegatives()));
        }
        for (BetaMemory last : lasts)
            last.push();
    }
    private BetaMemory addRule(RETERule rule, List<Condition> positives, List<List<Condition>> negatives) {
        List<LevelData> levels = getJoinData(positives, negatives);
        // Build RETE for positives
        Iterator<LevelData> iterData = levels.iterator();
        BetaMemory beta = BetaMemory.getDummy();
        for (Condition cond : positives) {
            LevelData data = iterData.next();
            FactHolder alpha = getProvider(cond.getTriple(), cond.getFilter());
            BetaJoinNode join = beta.resolveJoin(alpha, data.tests);
            beta = join.resolveMemory(data.binders);
        }
        // Append negative conditions
        TokenHolder last = beta;
        for (List<Condition> list : negatives) {
            if (list.size() == 1) {
                LevelData data = iterData.next();
                Condition cond = list.get(0);
                FactHolder alpha = getProvider(cond.getTriple(), cond.getFilter());
                last = new BetaNegativeJoinNode(alpha, last, data.tests);
            } else {
                BetaNCCEntryNode entry = new BetaNCCEntryNode(last);
                last = entry;
                for (Condition cond : list) {
                    LevelData data = iterData.next();
                    FactHolder alpha = getProvider(cond.getTriple(), cond.getFilter());
                    BetaJoinNode join = new BetaJoinNode(alpha, last, data.tests);
                    last = join.resolveMemory(data.binders);
                }
                last.addChild(entry.getExitNode());
                last = entry.getExitNode();
            }
        }
        // Append output node
        last.addChild(rule.getOutput());
        rules.put(rule, levels);
        return beta;
    }
    
    public void removeRule(RETERule rule)  {
        removeAllRules();
    }
    public void removeRules(Collection<RETERule> rules) {
        removeAllRules();
    }
    private void removeAllRules() {
        entryAlpha.clear();
        BetaMemory.getDummy().removeAllChildren();
        rules.clear();
    }
    
    private FactHolder getProvider(RDFTriple triple, Filter filter) {
        AlphaMemory alpha = entryAlpha.resolveMemory(triple, kb);
        if (filter != null) {
            FilterNode fn = new FilterNode(alpha, filter);
            alpha = fn.getChild();
        }
        return alpha;
    }
    
    private List<LevelData> getJoinData(List<Condition> positives, List<List<Condition>> negatives) {
        List<LevelData> tests = new ArrayList<LevelData>();
        List<XOWLVariableNode> vars = new ArrayList<XOWLVariableNode>();
        for (int i=0; i!=positives.size(); i++)
            tests.add(getjoinData(positives.get(i), vars));
        for (List<Condition> list : negatives)
            for (Condition neg : list)
                tests.add(getjoinData(neg, vars));
        return tests;
    }
    
    private LevelData getjoinData(Condition condition, Collection<XOWLVariableNode> vars) {
        LevelData currentData = new LevelData();
        RDFTriple triple = condition.getTriple();
        RDFNode node = triple.getSubject();
        if (node.getNodeType() == RDFNodeType.Variable) {
            XOWLVariableNode var = (XOWLVariableNode)node;
            if (vars.contains(var)) {
                RDFTripleField field = RDFTripleField.Subject;
                BetaJoinNodeTest test = new BetaJoinNodeTest(var, field);
                currentData.tests.add(test);
            } else {
                currentData.binders.add(new Binder(var, RDFTripleField.Subject));
            }
            vars.add(var);
        }

        node = triple.getProperty();
        if (node.getNodeType() == RDFNodeType.Variable) {
            XOWLVariableNode var = (XOWLVariableNode)node;
            if (vars.contains(var)) {
                RDFTripleField field = RDFTripleField.Property;
                BetaJoinNodeTest test = new BetaJoinNodeTest(var, field);
                currentData.tests.add(test);
            } else {
                currentData.binders.add(new Binder(var, RDFTripleField.Property));
            }
            vars.add(var);
        }

        node = triple.getObject();
        if (node.getNodeType() == RDFNodeType.Variable) {
            XOWLVariableNode var = (XOWLVariableNode)node;
            if (vars.contains(var)) {
                RDFTripleField field = RDFTripleField.Value;
                BetaJoinNodeTest test = new BetaJoinNodeTest(var, field);
                currentData.tests.add(test);
            } else {
                currentData.binders.add(new Binder(var, RDFTripleField.Value));
            }
            vars.add(var);
        }
        return currentData;
    }
}
